Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Initial implementation of auction dApp #2265

Draft
wants to merge 33 commits into
base: master
Choose a base branch
from

Conversation

mous1985
Copy link

@mous1985 mous1985 commented Jun 3, 2024

Initial Implementation of dAuction

Purpose

This pull request introduces the initial version (v0) of the auction dApp. The dApp allows users to create and manage auctions. It is designed to interact with an existing NFT realm to handle the state and ownership of NFTs, with future plans to integrate NFTs directly into the auction struct and automate transfers upon auction completion.
Main Features

  • Auction Creation: Users can create new auctions specifying the title, description, start time, end time, and minimum price.
  • Bidding: Users can place bids on active auctions. Each bid must be higher than the current highest bid.
  • Auction End: When the auction ends, the highest bidder wins.
    State Management: The auction state is managed by the realm, ensuring persistence and consistency across transactions.

How It Works

  • p/demo/auction:
    The auction package handles the core logic for creating and * managing auctions.
    It defines the Auction and Bid structs and provides methods for creating auctions, adding bids, and ending auctions.
    Future plans include integrating NFTs into the Auction struct to directly manage NFT auctions.

  • r/demo/auction:
    The auction realm maintains the state of all auctions using an AVL tree.
    It uses the auction package to handle auction operations and interacts with the NFT realm to manage NFT state and transfers.
    The realm exposes methods for creating auctions, placing bids, and rendering the current state.

  • Testing:
    The auction_test.gno file contains test cases that simulate auction creation, bidding, and ending.
    Tests ensure that the core functionalities work as expected, including state changes and interactions with the NFT realm.

Future Enhancements

Integrate NFTs: Future updates will include adding an nft.TokenID field to the Auction struct and enhancing the logic to support direct NFT auctions.
Deposits and Automatic Transfers:

  • NFT Deposit: The NFT will be deposited into the realm when the auction starts.
  • Bid Deposit: Bids will be deposited into the realm.
  • Automatic Transfer: Upon auction completion, the NFT will be automatically transferred to the highest bidder, and the highest bid amount will be transferred to the seller.

Additional Features: Plan to add more features such as auction cancellation, bid retraction, and more detailed auction statistics .

PS: This pull request lays the foundation for the auction dApp, and future enhancements can build on this initial implementation to add more features and improve functionality. Feel free to contribute and share any ideas :) .
@moul @leohhhn

Contributors' checklist...
  • Added new tests, or not needed, or not feasible
  • Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory
  • Updated the official documentation or not needed
  • No breaking changes were made, or a BREAKING CHANGE: xxx message was included in the description
  • Added references to related issues and PRs
  • Provided any useful hints for running manual tests
  • Added new benchmarks to generated graphs, if any. More info here.

@github-actions github-actions bot added the 🧾 package/realm Tag used for new Realms or Packages. label Jun 3, 2024
@mous1985 mous1985 changed the title dAuction Auction dapp Jun 3, 2024
@mous1985 mous1985 changed the title Auction dapp feat: Initial implementation of auction dApp Jun 3, 2024
@mous1985 mous1985 marked this pull request as ready for review June 4, 2024 12:32
@mous1985 mous1985 requested review from a team as code owners June 4, 2024 12:32
@mous1985 mous1985 requested review from gfanton and zivkovicmilos and removed request for a team June 4, 2024 12:32
Copy link
Contributor

@deelawn deelawn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auctions are a common thing in other web3 ecosystems, so thanks for adding this -- it's a good start.

What is the intended way to have users interact with auctions? I see that the realm returns an instance of the auction object to the caller. This may not be desirable because the person receiving the auction object could call the AddBid method, circumventing the the checks inside the the realm's AddBid function.

It seems to me like the realm would be the container for all auction objects and the auction specific logic could live within the package, so the error checking logic in the realm's AddBid function could be moved to the package, unless the intention is to give realms using the package full control over how these checks should look, though some of them seem pretty universal.

Also, I left a comment regarding the auction structs' fields being unexported. Exporting these fields would remove the need for all of the accessor methods, as well as let the realms using the package decide whether or not they want to let auction owners adjust auction details after they've been created. I could be persuaded either way; just something to consider.


// Main struct
type Auction struct {
title string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why the fields for these structs can't be exported so we can remove all of the getter methods? If auctions are made to be self contained within the realm itself (don't return any references to auctions), then it should be okay.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really sorry for the long delay in replying. 6936b8

examples/gno.land/p/demo/auction/auction.gno Outdated Show resolved Hide resolved
}

// NewBid creates a new Bid instance.
func NewBid(bidder std.Address, amount uint64) *Bid {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to get rid of this function -- not calling it will save gas and it's only used in one spot

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the NewBid() function and updated the AddBid() and the IsOwnedBy() functions 5e47aa9.

panic("Auction cannot end before the end time")
}
if a.bids.Size() == 0 {
panic("No bids placed")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? If the end time has passed for an auction then the realm prevents adding a bid, but this prevents marking the auction as ended, so the auction can never end.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced panic by ufmt.Errorf which handles other error cases dd5b4911

begin time.Time
end time.Time
price uint64
bids *avl.Tree // key: std.Address, value: Bid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is wrong; the key being used when adding new bids is a integer string.

Even so, why use an avl tree to represent bids instead of something like a slice? The bid struct contains the bidder's address already, so no need to use it as a key. Using a integer as a key is kind of like using a slice where the integer is the key by default.

}

// Mockable function to get current time
func now() time.Time {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to keep testing logic in the testing file. There is a pending PR that will allow you to change the timestamp from your test code; it would be good to use that when it is merged #569

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, please drop currentTime

}

// renderHomepage renders the homepage of the realm
func renderHomepage() string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rendering all auctions with all bids is a lot for one page. Consider having the homepage render links to each auction that, when rendered, display all the auction bids.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i updated the auction realm and i imported the mux package and added render pages for each auction state 9d634721

@mous1985
Copy link
Author

mous1985 commented Jun 5, 2024

Hi @deelawn,

Thank you for the detailed feedback! I appreciate your insights and suggestions.

I understand the possibility of users bypassing the controls inside the realm's AddBid function by calling methods directly on the auction object. To address this, I propose the following changes:

  • The realm will manage all interactions with auction objects. Instead of returning the auction instance, the realm will provide functions to interact with the auction, such as PlaceBid, EndAuction, etc.
  • Users will not receive direct references to the auction objects. Instead, they will interact with the auctions through the realm's interface, ensuring that all necessary checks are enforced.
  • Only the owner of the auction will be allowed to modify certain fields, such as the title and description. This ensures that unauthorized users cannot change the auction details.
  • it's true that rendering all the auctions with all the offers is a lot for one page. I will adjust it to render links to each auction, displaying all bids when the specific auction is viewed.

I am currently working on implementing these changes and will push an update shortly. Thank you for the constructive feedback and helping :)

Co-authored-by: deelawn <dboltz03@gmail.com>
Copy link
Author

@mous1985 mous1985 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @deelawn,

Thanks again for your insights,I wanted to let you know that I plan to integrate accesscontrol and timelock to give the bidders time to withdraw if there is something fishy functionalities into the logic of the auction.
Please let me know if you have any further suggestions or comments.

@zivkovicmilos
Copy link
Member

@mous1985
Can you please check why the CI is failing? 🙏

Copy link
Member

@zivkovicmilos zivkovicmilos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good 💯

The tests could use a bit more work, I think proptests would fit in nicely here.

Please see @deelawn's comments -- I've also left a couple 🙏

"gno.land/p/demo/avl"
)

// Main struct
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no needs for comments like these :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry for the delay in replying 🙏.I deleted the comment 😅 ,and replaced avlTree with a simple slice 6936b8

title string,
owner std.Address,
description string,
begin time.Time,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about having begin actually be time.Now?
This way you can decrease the param. It's only assuming you don't have auctions that start in the future, given that you emit an event when this constructor exits

end can be time.Duration if this is your preference.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the package with three auction states: “upcoming”, “ongoing”, and “closed”. That was my idea, so I could have auctions starting in the future.

examples/gno.land/p/demo/auction/auction.gno Outdated Show resolved Hide resolved
// GetBids returns an array of all the bids made in the auction.
func (a Auction) GetBids() []*Bid {
bids := make([]*Bid, 0, a.bids.Size())
a.bids.Iterate("", "", func(key string, value interface{}) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about adding some kind of pagination here, to circumvent an execution attack if there are many bids?

}

// IsOwner checks if the given address is the owner of the auction.
func (a Auction) IsOwnedBy(address std.Address) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use Ownable

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if !ok {
panic("auction does not exist")
}
auction := auc.(*auctionp.Auction)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can delegate these checks to the package AddBid instead

// renderHomepage renders the homepage of the realm
func renderHomepage() string {
var b bytes.Buffer
b.WriteString("<h1><center>Auctions</center></h1>\n\n")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not a simple #?

return b.String()
}

// Helper function to set the mock current time
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need these

begin := now().Add(1 * time.Second).Unix() // Auction begins in 1 second
end := now().Add(24 * time.Hour).Unix() // Auction ends in 24 hours
auc := NewAuction("Test Auction", "A simple test auction", begin, end, 100)
if auc == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use uassert here

setcurrentTime(now().Add(2 * time.Second))

// Place bids by different users
user1 := std.Address("user1")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use testutils for generating an address, not casting it directly like this

Copy link

codecov bot commented Aug 20, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 63.44%. Comparing base (04d5239) to head (327e3f6).
Report is 161 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2265      +/-   ##
==========================================
+ Coverage   60.23%   63.44%   +3.21%     
==========================================
  Files         562      551      -11     
  Lines       75091    82376    +7285     
==========================================
+ Hits        45230    52265    +7035     
- Misses      26481    26622     +141     
- Partials     3380     3489     +109     
Flag Coverage Δ
contribs/gnodev 61.16% <ø> (-0.24%) ⬇️
contribs/gnofaucet 14.82% <ø> (-0.50%) ⬇️
gno.land 73.70% <ø> (+8.93%) ⬆️
gnovm 67.74% <ø> (+3.40%) ⬆️
misc/genstd 79.72% <ø> (-0.83%) ⬇️
tm2 62.35% <ø> (+0.30%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@gfanton
Copy link
Member

gfanton commented Aug 26, 2024

@mous1985, What is the status on this? Can you check and address the comments?

@Kouteki Kouteki added review/triage-pending PRs opened by external contributors that are waiting for the 1st review and removed review/triage-pending PRs opened by external contributors that are waiting for the 1st review labels Oct 3, 2024
Comment on lines 23 to 26
router.HandleFunc("create/", renderCreateAuction)
router.HandleFunc("upcoming/", renderUpcomingAuctions)
router.HandleFunc("ongoing/", renderOngoingAuctions)
router.HandleFunc("/auction/{id}", renderAuctionDetails)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this weird?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the auction state 22285536

Comment on lines 80 to 96
// renderCreateAuction renders the create auction page
func renderCreateAuction(res *mux.ResponseWriter, req *mux.Request) {
var b bytes.Buffer
b.WriteString("<html><body>")

b.WriteString("#Create New Auction\n")
b.WriteString("<form action='/create' method='post'>\n")
b.WriteString("Title: <input type='text' name='title'><br>\n")
b.WriteString("Description: <input type='text' name='description'><br>\n")
b.WriteString("Begin: <input type='datetime-local' name='begin'><br>\n")
b.WriteString("End: <input type='datetime-local' name='end'><br>\n")
b.WriteString("Starting Price: <input type='number' name='price'><br>\n")
b.WriteString("<input type='submit' value='Create Auction'>\n")
b.WriteString("</form>\n")
b.WriteString("</body></html>")
res.Write(b.String())
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer supported in gnoweb; we should rather look to have something using helplink.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for that 🙏🏽 da99a7a3

Comment on lines 72 to 76
var b bytes.Buffer
b.WriteString("<h1><center>Auction Home</center></h1>\n\n")
b.WriteString("<h2><a href='/r/demo/auction:create/'>Create New Auction</a></h2>\n\n")
b.WriteString("<h2><a href='/r/demo/auction:upcoming/'>Upcoming Auctions</a></h2>\n\n")
b.WriteString("<h2><a href='/r/demo/auction:ongoing/'>Ongoing Auctions</a></h2>\n\n")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What did markdown do to you? 😿

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

da99a7a3 😸

Comment on lines 30 to 33
var (
auctionList []*Auction
currentTime time.Time
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These don't make sense in the package / are not used in the test

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. it's removed 61066d74

"gno.land/p/demo/ufmt"
)

// Auction struct
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Auction struct" is not particularly useful. Some general suggestions: https://go.dev/doc/comment#type

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this application not work with OrigSend, ie. expecting the user to actually make a deposit when they make a bid, in order to be useful?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

20d7aad
327e3f6

  1. bidding system
    When a user places a bid and deposits tokens in the kingdom, they can simply add the difference if they wish to increase their bid later.
  2. For EndAuction: Bidders who do not win the auction will be automatically reimbursed once the auction is over, while the winner's funds will be transferred to the auction owner.

I've updated all the tests

@thehowl
Copy link
Member

thehowl commented Nov 6, 2024

Apologies, I do realise that some part of my comments at least is tackled in the updated code of the repository

@leohhhn
Copy link
Contributor

leohhhn commented Nov 12, 2024

@mous1985

If you're working on a separate repo for this, can you do your work there, put this PR in draft, and then when you're done there copy everything over here? In order to not duplicate work. Also, if you're still actively working on the PR, switch it to draft.

Also, please change the namespace of this app to your own namespace.

cc @thehowl

@leohhhn leohhhn marked this pull request as draft November 12, 2024 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🧾 package/realm Tag used for new Realms or Packages.
Projects
Status: In Progress
Status: In Progress
Development

Successfully merging this pull request may close these issues.

7 participants